package top.easyblog.core.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.validator.routines.UrlValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import top.easyblog.common.bean.UrlMappingBean;
import top.easyblog.common.constant.RedisDistributeKeyConstants;
import top.easyblog.common.enums.CompressionCodeStatus;
import top.easyblog.common.enums.MappingStatus;
import top.easyblog.common.request.CreateUrlMappingRequest;
import top.easyblog.common.request.QueryUrlMappingRequest;
import top.easyblog.core.annotation.DLock;
import top.easyblog.core.atomic.AtomicUrlMappingService;
import top.easyblog.core.config.CustomConfiguration;
import top.easyblog.core.converter.BeanMapper;
import top.easyblog.core.dao.auto.model.UrlMapping;
import top.easyblog.core.mq.RedisMessageProducer;
import top.easyblog.core.mq.RedisMessageTopic;
import top.easyblog.core.mq.bean.UrlMappingMessage;
import top.easyblog.core.support.bloom.RedissonBloomFilterSupport;
import top.easyblog.core.support.cache.MappingCacheManager;
import top.easyblog.core.support.context.UrlMappingBuildContext;
import top.easyblog.core.support.filter.TransformContext;
import top.easyblog.core.support.filter.TransformFilterChain;
import top.easyblog.core.support.filter.TransformFilterChainFactory;
import top.easyblog.core.util.AssertUtil;
import top.easyblog.core.util.ConcurrentUtils;
import top.easyblog.core.util.EncryptUtils;
import top.easyblog.core.util.JsonUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

import static top.easyblog.common.constant.Constants.HTTPS_PROTOCOL;
import static top.easyblog.common.constant.Constants.HTTP_PROTOCOL;

/**
 * @author: frank.huang
 * @date: 2023-12-17 23:27
 */
@Slf4j
@Service
public class UrlMappingService {


    @Autowired
    private AtomicUrlMappingService atomicUrlMappingService;

    @Autowired
    private CompressCodeService compressCodeService;

    @Autowired
    private BeanMapper beanMapper;

    @Autowired
    private CustomConfiguration configuration;

    @Autowired
    private TransformFilterChainFactory transformFilterChainFactory;

    @Autowired
    private MappingCacheManager mappingCacheManager;

    @Autowired
    private RedissonBloomFilterSupport redissonBloomFilterSupport;

    @Autowired
    private RedisMessageProducer messageProducer;

    @Autowired
    private ThreadPoolTaskExecutor dispatchTaskExecutor;


    private final UrlValidator urlValidator = new UrlValidator(new String[]{
            HTTP_PROTOCOL, HTTPS_PROTOCOL}
    );


    /**
     * 创建映射
     *
     * @param request
     * @return
     */
    public UrlMappingBean createUrlMapping(CreateUrlMappingRequest request) {
        AssertUtil.isTrue(urlValidator.isValid(request.getSourceUrl()), String.format("链接[%s]非法", request.getSourceUrl()));

        String sourceUrlDigest = EncryptUtils.SHA256(request.getSourceUrl());
        if (redissonBloomFilterSupport.contains(sourceUrlDigest)) {
            log.info("源链接：{} ,摘要：{} 可能已经存在对应映射", request.getSourceUrl(), sourceUrlDigest);
            UrlMapping urlMapping = mappingCacheManager.getCacheBySourceUrlDigest(sourceUrlDigest);
            return beanMapper.convertUrlMapping2Bean(urlMapping);
        }

        //创建映射
        return createMapping(request, sourceUrlDigest);
    }


    @DLock(key = RedisDistributeKeyConstants.CREATE_URL_SHORTLINK_MAPPING_KEY, extKey = "#sourceUrlDigest")
    public UrlMappingBean createMapping(CreateUrlMappingRequest request, String sourceUrlDigest) {
        String compressCode = compressCodeService.getAvailableCompressCode();
        AssertUtil.isTrue(StringUtils.isNotBlank(compressCode), "当前不存在可用压缩码，请稍后重试~");

        String tinyUrl = String.format("%s://%s/%s", HTTP_PROTOCOL, configuration.getDefaultLinkDomain(), compressCode);
        UrlMappingBuildContext context = UrlMappingBuildContext.builder()
                .sourceUrl(request.getSourceUrl())
                .description(request.getDescription())
                .tinyUrl(tinyUrl)
                .compressionCode(compressCode)
                .sourceUrlDigest(sourceUrlDigest)
                .tinyUrlDigest(EncryptUtils.SHA256(tinyUrl))
                .status(MappingStatus.AVAILABLE.getValue())
                .build();

        return doCreateUrlMapping(context);
    }


    public UrlMappingBean doCreateUrlMapping(UrlMappingBuildContext context) {
        sendUrlMappingCreateMessage(context);

        UrlMapping urlMapping = beanMapper.convertRequest2UrlMapping(context);
        mappingCacheManager.refreshCache(urlMapping);
        redissonBloomFilterSupport.add(context.getSourceUrlDigest());
        return beanMapper.convertUrlMapping2Bean(urlMapping);
    }

    private void sendUrlMappingCreateMessage(UrlMappingBuildContext context) {
        UrlMappingMessage.UrlMappingMessageBean mappingMessageBean = beanMapper.convertUrlMapping2KfMessageBean(context);
        UrlMappingMessage urlMappingMessage = UrlMappingMessage.builder().urlMapping(mappingMessageBean).timestamp(System.currentTimeMillis())
                .build();
        messageProducer.sendRedisMessage(JsonUtils.toJSONString(urlMappingMessage), RedisMessageTopic.URL_MAPPING_CREATE);
    }


    public void saveUrlMapping(UrlMappingMessage.UrlMappingMessageBean urlMappingMessageBean) {
        AssertUtil.notNull(urlMappingMessageBean, "Empty urlMappingMessageBean");
        UrlMapping urlMapping = beanMapper.convertRequest2UrlMapping(urlMappingMessageBean);
        atomicUrlMappingService.insertOne(urlMapping);
        compressCodeService.updateStatus(urlMappingMessageBean.getCompressionCode(), CompressionCodeStatus.USED.getValue());
    }

    /**
     * 访问映射
     *
     * @param code
     * @param request
     * @return
     */
    public void dispatch(String code, HttpServletRequest request, HttpServletResponse response) {
        TransformContext transformContext = TransformContext.assemble(code, request, response);
        processTransform(transformContext);
        dispatchTaskExecutor.execute(transformContext.getRedirectAction());
    }


    public void processTransform(TransformContext transformContext) {
        long start = System.nanoTime();
        if (log.isDebugEnabled()) {
            log.debug("Entry TransformFilterChain...");
        }
        TransformFilterChain chain = transformFilterChainFactory.buildTransformFilterChain(transformContext);
        try {
            chain.doFilter(transformContext);
        } finally {
            chain.release();
            transformContext.release();
            if (log.isDebugEnabled()) {
                log.debug("Exit TransformFilterChain,cost {} ms...", TimeUnit.NANOSECONDS.toMillis((System.nanoTime() - start)));
            }
        }
    }


    public UrlMapping queryByRequest(QueryUrlMappingRequest request) {
        return atomicUrlMappingService.queryByRequest(request);
    }


}
